package com.jiangyifen.robot.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.aliyuncs.chatbot.model.v20171011.ChatResponse;
import com.aliyuncs.chatbot.model.v20171011.ChatResponse.Message;
import com.aliyuncs.chatbot.model.v20171011.ChatResponse.Message.Recommend;
import com.jiangyifen.robot.consts.ContentType;
import com.jiangyifen.robot.consts.NlpHit;
import com.jiangyifen.robot.consts.NodeType;
import com.jiangyifen.robot.core.asr.AliyunASRClient;
import com.jiangyifen.robot.core.nlp.AliyunNLPClient;
import com.jiangyifen.robot.core.nlp.SentimentResponse;
import com.jiangyifen.robot.core.session.RobotSession;
import com.jiangyifen.robot.core.sipphone.SipPhoneClient;
import com.jiangyifen.robot.core.sipphone.SipPhoneClientImpl;
import com.jiangyifen.robot.core.sipphone.SipPhoneConfig;
import com.jiangyifen.robot.core.tts.AliyunTTSClient;
import com.jiangyifen.robot.entity.ChatNodeEntity;
import com.jiangyifen.robot.entity.ChatRecordEntity;
import com.jiangyifen.robot.entity.RobotYunXiaoMiEntity;
import com.jiangyifen.robot.logger.WsLogger;
import com.jiangyifen.robot.sdk.event.HumanSentenceCompleteEvent;
import com.jiangyifen.robot.sdk.event.HumanSilenceEvent;
import com.jiangyifen.robot.sdk.event.HumanSpeakingByteStreamEvent;
import com.jiangyifen.robot.sdk.event.HumanSpeakingEvent;
import com.jiangyifen.robot.sdk.event.RobotSayNodeEvent;
import com.jiangyifen.robot.sdk.event.RobotSentenceCompleteEvent;
import com.jiangyifen.robot.sdk.event.RobotSpeakingEvent;
import com.jiangyifen.robot.sdk.event.SipCalleePickupEvent;
import com.jiangyifen.robot.sdk.event.SipErrorEvent;
import com.jiangyifen.robot.sdk.event.SipIncomingCallEvent;
import com.jiangyifen.robot.sdk.event.SipRegisterFailedEvent;
import com.jiangyifen.robot.sdk.event.SipRegisterSuccessfulEvent;
import com.jiangyifen.robot.sdk.event.SipRegisteringEvent;
import com.jiangyifen.robot.sdk.event.SipRemoteHangupEvent;
import com.jiangyifen.robot.sdk.event.SipRingingEvent;
import com.jiangyifen.robot.sdkserver.RobotSdkServer;
import com.jiangyifen.robot.service.RobotSessionService;

public class RobotImpl implements Robot {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private long lastSilenceTimestamp = 0;

	private boolean registerSuccessful;

	private boolean calling;

	private AliyunTTSClient aliyunTTSClient;

	private AliyunASRClient aliyunASRClient;

	private AliyunNLPClient aliyunNLPClient;

	private SipPhoneClient sipPhoneClient;

	private RobotSessionService sessionService;

	private RobotYunXiaoMiEntity robotEntity;

	private Long scenarioId;

	private RobotSession currentSession;// 机器人当前的会话，通话开始时新建

	private PipedOutputStream pos;

	private PipedInputStream pis;

	private boolean asrFlag = true;

	private String wavHome;

	public RobotImpl(RobotSessionService sessionService, Long scenarioId, RobotYunXiaoMiEntity robotEntity,
			String localIp, String ttsTempPath, String wavHome) throws IOException {

		this.scenarioId = scenarioId;
		this.sessionService = sessionService;

		this.robotEntity = robotEntity;

		SipPhoneConfig sipPhoneConfig = new SipPhoneConfig(robotEntity, localIp);
		this.sipPhoneClient = new SipPhoneClientImpl(sipPhoneConfig);
		sipPhoneClient.setRobot(this);

		this.wavHome = wavHome;

		this.aliyunASRClient = new AliyunASRClient();
		aliyunASRClient.setRobot(this);

		this.aliyunTTSClient = new AliyunTTSClient(ttsTempPath);
		aliyunTTSClient.setRobot(this);

		this.aliyunNLPClient = new AliyunNLPClient();
		aliyunNLPClient.setRobot(this);
	}

	@Override
	public Long getId() {
		return robotEntity.getId();
	}

	@Override
	public RobotYunXiaoMiEntity getRobotEntity() {
		return robotEntity;
	}

	@Override
	public SipPhoneClient getSipPhoneClient() {
		return sipPhoneClient;
	}

	@Override
	public AliyunASRClient getAliyunASRClient() {
		return aliyunASRClient;
	}

	@Override
	public RobotSession getNewSession() {
		this.currentSession = sessionService.getNewSession(scenarioId, this);
		return currentSession;
	}

	@Override
	public RobotSession getCurrentSession() {
		if (currentSession == null) {
			this.currentSession = sessionService.getNewSession(scenarioId, this);
		}
		return currentSession;
	}

	@Override
	public long getLastSilenceTimestamp() {
		return lastSilenceTimestamp;
	}

	@Override
	public void setLastSilenceTimestamp(long lastSilenceTimestamp) {
		this.lastSilenceTimestamp = lastSilenceTimestamp;
	}

	@Override
	public boolean isRegisterSuccessful() {
		return registerSuccessful;
	}

	@Override
	public void setRegisterSuccessful(boolean registerSuccessful) {
		this.registerSuccessful = registerSuccessful;
	}

	@Override
	public boolean isCalling() {
		return calling;
	}

	@Override
	public void setCalling(boolean calling) {
		this.calling = calling;
	}

	/////// private ////////

	private void saveHumanChatNode(String text, String nlpHit) {
		// 记录人类说的chat record

		try {
			RobotSession currentSession = getCurrentSession();

			ChatRecordEntity chatRecord = new ChatRecordEntity();
			chatRecord.setCallId(currentSession.getCallId());
			chatRecord.setVmwCallUuid(currentSession.getVmwCallUuid());
			chatRecord.setRobotId(getId());
			chatRecord.setPhoneNumber(currentSession.getPhoneNumber());
			chatRecord.setScenarioId(currentSession.getScenarioEntity().getId());
			chatRecord.setNodeCode(currentSession.getCurrentChatNodeEntity().getNodeCode());
			chatRecord.setText(text);
			chatRecord.setSayByHuman(true);
			chatRecord.setNlpHit(nlpHit);

			sessionService.saveChatRecord(chatRecord);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	private boolean isImportant(String input) {
		// 返回true，代表是重要的内容，需要理会
		// TODO 过滤语气词等无意义的内容。
		if (StringUtils.isBlank(input)) {
			return false;
		}
		if (input.length() < 3) {
			logger.info("系统说：检测到无意义短语，不打断、不处理");
			return false;
		}

		List<String> wordlist = new ArrayList<>();
		wordlist.add("你好");
		wordlist.add("您好");
		wordlist.add("喂");
		for (String word : wordlist) {
			if (input.length() < 4 && input.contains(word)) {
				return false;
			}
		}

		return true;
	}

	private String parseNodeCode(String content) {
		String code = "";
		if (StringUtils.isNotBlank(content) && content.startsWith("[")) {
			try {
				code = content.substring(content.indexOf("[") + 1, content.indexOf("]"));
			} catch (Exception e) {
				logger.error("ERROR! content = " + content);
				logger.error(e.getMessage(), e);
			}
		}
		return code;
	}

	private void sentimentAnswer(String text) {
		logger.info("系统说：先判断关键词，再根据人类说的话做情感判断，根据是否命中关键词，以及情绪的正面、负面、中性，来决定下一node，并say该node");

		String nlpHit = NlpHit.ERROR;

		ChatNodeEntity currentChatNodeEntity = currentSession.getCurrentChatNodeEntity();

		if (currentChatNodeEntity == null) {
			sayText("当前节点为空。你是不是没有配置对话场景和对话节点？或者配置有误", true);
			logger.warn("系统说：当前节点为空。你是不是没有配置对话场景和对话节点？或者配置有误");
			saveHumanChatNode(text, nlpHit);
			return;
		}

		// 1）确定nextNode

		String keywordListString1 = currentChatNodeEntity.getKeywordList1();
		List<String> keywordList1 = JSON.parseArray(keywordListString1, String.class);
		boolean hitKeyword1 = false;
		if (keywordList1 == null) {
			keywordList1 = JSON.parseArray("[]", String.class);
		}
		for (String keyword : keywordList1) {
			hitKeyword1 = text.contains(keyword);
			if (hitKeyword1) {

				logger.info("系统说：命中关键词1：{} （{}）", keyword, text);
				break;
			}
		}

		String keywordListString2 = currentChatNodeEntity.getKeywordList2();
		List<String> keywordList2 = JSON.parseArray(keywordListString2, String.class);
		boolean hitKeyword2 = false;
		if (keywordList2 == null) {
			keywordList2 = JSON.parseArray("[]", String.class);
		}
		for (String keyword : keywordList2) {
			hitKeyword2 = text.contains(keyword);
			if (hitKeyword2) {
				logger.info("系统说：命中关键词2：{} （{}）", keyword, text);
				break;
			}
		}

		String keywordListString3 = currentChatNodeEntity.getKeywordList3();
		List<String> keywordList3 = JSON.parseArray(keywordListString3, String.class);
		boolean hitKeyword3 = false;
		if (keywordList3 == null) {
			keywordList3 = JSON.parseArray("[]", String.class);
		}
		for (String keyword : keywordList3) {
			hitKeyword3 = text.contains(keyword);
			if (hitKeyword3) {
				logger.info("系统说：命中关键词3：{} （{}）", keyword, text);
				break;
			}
		}

		String nextNodeCode;

		if (hitKeyword1) {
			nextNodeCode = currentChatNodeEntity.getNextNodeCodeForKeyword1();
			nlpHit = NlpHit.KEYWORD_1;
		} else if (hitKeyword2) {
			nextNodeCode = currentChatNodeEntity.getNextNodeCodeForKeyword2();
			nlpHit = NlpHit.KEYWORD_2;
		} else if (hitKeyword3) {
			nextNodeCode = currentChatNodeEntity.getNextNodeCodeForKeyword3();
			nlpHit = NlpHit.KEYWORD_3;
		} else {
			SentimentResponse sentiment = this.aliyunNLPClient.sentiment(text);
			int textPolarity = 1;
			try {
				textPolarity = Integer.valueOf(sentiment.getData().get("text_polarity"));
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
			}
			logger.info("系统说：人类刚才说[" + text + "]，其情感判断为（1表示正面，0表示中性，-1表示负面）：" + textPolarity);

			if (textPolarity == 1) {
				nextNodeCode = currentChatNodeEntity.getNextNodeCodeForGoodAnswer();
				nlpHit = NlpHit.TEXTPOLARITY_GOOD;
			} else if (textPolarity == 0) {
				nextNodeCode = currentChatNodeEntity.getNextNodeCodeForNormalAnswer();
				nlpHit = NlpHit.TEXTPOLARITY_NORMAL;
			} else if (textPolarity == -1) {
				nextNodeCode = currentChatNodeEntity.getNextNodeCodeForBadAnswer();
				nlpHit = NlpHit.TEXTPOLARITY_BAD;
			} else {
				// 对于情感判断出错的情况，应该默认一个情感。比如，正面 or 中性
				logger.warn("系统说：情感判断出错, textPolarity={}, text={}, sentiment={}", textPolarity, text,
						JSON.toJSONString(sentiment));
				nextNodeCode = currentChatNodeEntity.getNextNodeCodeForNormalAnswer();
			}
		}

		saveHumanChatNode(text, nlpHit);

		// 2）执行nextNode

		ChatNodeEntity chatNodeEntity = sessionService.getChatNodeEntity(nextNodeCode);
		if (chatNodeEntity != null) {
			sayChatNode(chatNodeEntity);
		} else {
			sayText("下个节点为空.你是不是没有配置对话场景和对话节点？或者配置有误", true);
			logger.warn("系统说：下个节点为空.你是不是没有配置对话场景和对话节点？或者配置有误");
		}

	}

	private void robotStartTalking() {
		// 电话接通后，让机器人先开口说话
		try {
			RobotSession robotSession = getCurrentSession();
			ChatNodeEntity firstChatNodeEntity = robotSession.getFirstChatNodeEntity();
			if (firstChatNodeEntity != null) {
				sayChatNode(firstChatNodeEntity);
			} else {
				sayText("对话场景没有配置首节点，请检查", false);
			}

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	private void startASR() {
		try {
			// 启动asr
			// piped input output stream
			// sip
			// phone收到的语音流从pos这个outputStream.write(byte[])出来,进入到pis这个inputStream中。
			// pis作为asr识别语音的数据源
			pos = new PipedOutputStream();
			pis = new PipedInputStream();
			pis.connect(pos);
			aliyunASRClient.startup();
			aliyunASRClient.process(pis);

			logger.debug("aliyunASRClient.startup()");
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	private void stopASR() {
		try {
			// 断开asr连接
			aliyunASRClient.shutdonw();
			logger.debug("aliyunASRClient.shutdonw()");
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void sayChatNode(ChatNodeEntity chatNodeEntity) {

		if (chatNodeEntity == null) {
			sayText("节点为空，请检查日志", false);
			return;
		}

		// 更新一下当前CurrentChatNode
		getCurrentSession().setCurrentChatNodeEntity(chatNodeEntity);
		if (chatNodeEntity.getIsMainNode()) {
			getCurrentSession().setLastMainChatNodeEntity(chatNodeEntity);
		}

		// 根据chatNode是否可以被打断或是否需要不间断执行后续action来配置机器人当前打断flag。
		// 记得node说完后，将打断flag恢复成可打断
		if (!chatNodeEntity.getCanBeInterrupt() || StringUtils.isNotBlank(chatNodeEntity.getActionType())) {
			logger.info("系统说：暂停ASR处理，因为当前正对话节点被设置为不能打断，或配有后续动作");
			WsLogger.SYSTEM.debug(getId().toString(), "系统说：暂停ASR处理，因为当前正对话节点被设置为不能打断，或配有后续动作");

			asrFlag = false;
		}

		// “说”内容
		String contentType = chatNodeEntity.getContentType();
		if (ContentType.TTS.getType().equals(contentType)) {
			sayText(chatNodeEntity.getContentText(), chatNodeEntity.getCanBeInterrupt(), chatNodeEntity.getId(),
					chatNodeEntity.getNodeCode());
		} else if (ContentType.FILE.getType().equals(contentType)) {
			sayFile(chatNodeEntity.getContentFilePath(), chatNodeEntity.getCanBeInterrupt(), chatNodeEntity.getId(),
					chatNodeEntity.getNodeCode());
		} else if (ContentType.URL.getType().equals(contentType)) {
			sayUrl(chatNodeEntity.getContentUrl(), chatNodeEntity.getCanBeInterrupt(), chatNodeEntity.getId(),
					chatNodeEntity.getNodeCode());
		} else {
			logger.error("unkonw contentType: " + contentType);
		}

		// 记录机器人说的chat record
		try {
			RobotSession currentSession = getCurrentSession();

			ChatRecordEntity chatRecord = new ChatRecordEntity();
			chatRecord.setCallId(currentSession.getCallId());
			chatRecord.setVmwCallUuid(currentSession.getVmwCallUuid());
			chatRecord.setRobotId(getId());
			chatRecord.setPhoneNumber(currentSession.getPhoneNumber());
			chatRecord.setScenarioId(currentSession.getScenarioEntity().getId());
			chatRecord.setNodeCode(currentSession.getCurrentChatNodeEntity().getNodeCode());
			chatRecord.setText(currentSession.getCurrentChatNodeEntity().getContentText());
			chatRecord.setSayByHuman(false);
			chatRecord.setNlpHit(NlpHit.NO_NLP);

			sessionService.saveChatRecord(chatRecord);

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// 发送event
		RobotSayNodeEvent event = new RobotSayNodeEvent();
		event.setRobotId(getId());
		event.setNodeId(chatNodeEntity.getId());
		event.setNodeCode(chatNodeEntity.getNodeCode());
		event.setContentText(chatNodeEntity.getContentText());

		onRobotSayNode(event);

	}

	@Override
	public void sayFile(String filePath, Boolean canBeInterrupt) {
		sayFile(filePath, canBeInterrupt, 0L, "");
	}

	private void sayFile(String filePath, Boolean canBeInterrupt, Long nodeId, String nodeCode) {
		String path = wavHome + filePath;
		File file = new File(path);
		sayFile(file, canBeInterrupt);
	}

	@Override
	public void sayFile(File file, Boolean canBeInterrupt) {
		sayFile(file, canBeInterrupt, 0L, "");
	}

	private void sayFile(File file, Boolean canBeInterrupt, Long nodeId, String nodeCode) {
		if (file == null) {
			return;
		}

		InputStream inputStream = null;

		try {
			inputStream = new FileInputStream(file);
			inputStream.read(new byte[320], 0, 320);

			sayInputStream(inputStream, canBeInterrupt, nodeId, nodeCode);

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		} finally {
			try {
				if (inputStream != null) {
					inputStream.close();
				}
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
		}
	}

	@Override
	public void sayUrl(String url, Boolean canBeInterrupt) {
		sayUrl(url, canBeInterrupt, 0L, "");
	}

	private void sayUrl(String url, Boolean canBeInterrupt, Long nodeId, String nodeCode) {
		if (StringUtils.isBlank(url)) {
			return;
		}

		InputStream inputStream = null;

		try {
			URL u = new URL(url);
			inputStream = u.openStream();

			sayInputStream(inputStream, canBeInterrupt, nodeId, nodeCode);

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		} finally {
			try {
				if (inputStream != null) {
					inputStream.close();
				}
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
		}
	}

	private void sayByte(byte[] data, int offset, int length, Long nodeId, String nodeCode) {
		byte[] dest = new byte[length];
		System.arraycopy(data, offset, dest, offset, length);

		VoiceData voiceData = new VoiceData();
		voiceData.setRobotId(getId());
		voiceData.setNodeId(nodeId);
		voiceData.setNodeCode(nodeCode);
		voiceData.setData(dest);

		if (data.length == 1) {
			voiceData.setFinish(true);
		}

		sipPhoneClient.say(voiceData);
	}

	private synchronized void sayInputStream(InputStream inputStream, Boolean canBeInterrupt, Long nodeId,
			String nodeCode) {
		try {
			byte[] voiceDataBuffer = new byte[VoiceDataParam.BYTE_LENGTH];
			int len;
			while ((len = inputStream.read(voiceDataBuffer)) > 0) {

				sayByte(voiceDataBuffer, 0, len, nodeId, nodeCode);
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// 特殊的byte[1]，表示说话结束
		sayByte(new byte[1], 0, 1, nodeId, nodeCode);
	}

	@Override
	public void sayText(String text, Boolean canBeInterrupt) {
		sayText(text, canBeInterrupt, 0L, "");
	}

	private void sayText(String text, Boolean canBeInterrupt, Long nodeId, String nodeCode) {
		File file = aliyunTTSClient.tts(text);
		if (file != null) {
			sayFile(file, canBeInterrupt, nodeId, nodeCode);
			file.delete();
		}
	}

	@Override
	public void stopTalking() {
		// 命令机器人停止说话，一定是想要机器人保持正常的ASR状态的，所以要把asrFlag调到true
		logger.info("系统说：人类正在说话，机器人说话被打断");
		WsLogger.SYSTEM.debug(getId().toString(), "系统说：人类正在说话，机器人说话被打断");

		asrFlag = true;
		sipPhoneClient.clearVoiceDataQueue();
	}

	@Override
	public void register() {
		sipPhoneClient.register();
	}

	@Override
	public void unregister() {
		sipPhoneClient.unregister();
	}

	@Override
	public String call(String phoneNumber) {
		String callId = sipPhoneClient.call(phoneNumber);

		RobotSession robotSession = getNewSession();
		robotSession.setPhoneNumber(phoneNumber);
		robotSession.setCallId(callId);

		return callId;
	}

	@Override
	public String originate() {
		// TODO vmw 支持
		return "需要VMW呼叫中心中间件支持 originate";
	}

	@Override
	public void dtmf(final char digit) {
		sipPhoneClient.dtmf(digit);
	}

	@Override
	public void rejectCall() {
		setLastSilenceTimestamp(0);
		setCalling(false);
		currentSession = null;
		sipPhoneClient.rejectCall();
		stopASR();
	}

	@Override
	public void hangup() {
		setLastSilenceTimestamp(0);
		setCalling(false);
		currentSession = null;
		sipPhoneClient.hangup();
		stopASR();
	}

	@Override
	public void answer() {
		setCalling(true);
		sipPhoneClient.answer();
	}

	@Override
	public void onHumanSpeakingByteStream(HumanSpeakingByteStreamEvent event) {
		// logger.debug(JSON.toJSONString(event));
		// 这个日志太多了，刷屏，不打了

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ================
		// === 处理业务 ===
		// ================
		// 送去内置的ASR

		try {
			if (pos != null) {
				pos.write(event.getData());
			} else {
				logger.debug("pos is null");
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

	}

	@Override
	public void onHumanSpeaking(HumanSpeakingEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {
			setLastSilenceTimestamp(0);

			String text = event.getText();

			// 1)如果机器人正在说话的节点可以被打断，那么机器人就停止说话
			// 如果当前机器人说的话比较重要，不希望被打断，那么久让机器人把话说完
			// 同时，避免“嗯”“啊”“哦”这种词频繁打断，需要内容满足一定条件才打断
			RobotSession currentSession = this.getCurrentSession();
			ChatNodeEntity currentChatNodeEntity = currentSession.getCurrentChatNodeEntity();
			if (currentChatNodeEntity != null && currentChatNodeEntity.getCanBeInterrupt() && isImportant(text)) {
				stopTalking();
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onHumanSentenceComplete(HumanSentenceCompleteEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		String nlpHit;
		try {
			setLastSilenceTimestamp(System.currentTimeMillis());

			String text = event.getText();

			if (StringUtils.isBlank(text)) {
				return;
			}

			logger.info("人类说：{}", text);

			// 如果当前节点不允许被打断，那么人类说什么，机器人直接忽略（不送去做语义处理）
			if (!asrFlag) {
				logger.info("系统说：检测到有人说话。但是，当前的asrFlag=false。所以这里忽略人类说的话，不做任何处理。");
				WsLogger.SYSTEM.debug(getId().toString(), "系统说：检测到有人说话。但是，当前的asrFlag=false。所以这里忽略人类说的话，不做任何处理。");
				return;
			}

			// 如果人类说的是语气词等无意义的内容，则直接忽略，不做NLP处理
			if (!isImportant(text)) {
				return;
			}

			// 1)将整句送去NLP，获取意图和答案。了解答案后，继续说话

			// ===== 送去做NLP =====
			ChatResponse chatResponse = aliyunNLPClient.chat(getCurrentSession().getId(), text, null);

			List<Message> messages = chatResponse.getMessages();
			if (messages.size() > 0) {
				// 1.1)若命中知识库，先回答问题
				Message message = chatResponse.getMessages().get(0);
				String type = message.getType();

				if ("Knowledge".equals(type)) {
					// 精确命中一条知识库
					nlpHit = NlpHit.KNOWLEDGE;
					saveHumanChatNode(text, nlpHit);

					String summary = message.getKnowledge().getSummary();
					logger.info("系统说：精确命中一条知识库，对应的 nodeCode = " + summary);
					WsLogger.SYSTEM.debug(getId().toString(), "系统说：精确命中一条知识库，对应的 nodeCode = " + summary);
					// 配置方案中，阿里云平台的知识库内容，将会被配置成一个 ChatNodeEntity.nodeCode
					// 举例问题：如何申请鼠标？
					// 举例答案：[1000]找行政部填写申请表格。
					// nodeCode is 1000
					String nodeCode = parseNodeCode(summary);
					ChatNodeEntity chatNode = sessionService.getChatNodeEntity(nodeCode);

					if (chatNode == null) {
						logger.info("系统说：节点编号可能填错了，数据库里没找到对应的节点。错误的节点编号是:" + nodeCode);
						WsLogger.SYSTEM.debug(getId().toString(), "系统说：节点编号可能填错了，数据库里没找到对应的节点。错误的节点编号是:" + nodeCode);
						sayText("节点编号可能填错了，数据库里没找到对应的节点。错误的节点编号是:" + nodeCode, false);
						return;
					}

					sayChatNode(chatNode);
				} else if ("Recommend".equals(type)) {
					// 命中多条知识库，此时可以只说第一条命中的建议，或把所有命中知识库的答案都挨个说一遍
					nlpHit = NlpHit.KNOWLEDGE_RECOMMAND;
					saveHumanChatNode(text, nlpHit);

					logger.info("系统说：命中一条或多条知识库，一条一条处理。。。");
					List<Recommend> recommends = message.getRecommends();
					for (Recommend recommend : recommends) {
						String knowledgeId = recommend.getKnowledgeId();
						String title = recommend.getTitle();
						chatResponse = aliyunNLPClient.chat(getCurrentSession().getId(), title, knowledgeId);

						String summary = chatResponse.getMessages().get(0).getKnowledge().getSummary();
						logger.debug(summary);

						String nodeCode = parseNodeCode(summary);
						ChatNodeEntity chatNode = sessionService.getChatNodeEntity(nodeCode);

						if (chatNode == null) {
							logger.warn("系统说：节点编号可能填错了，数据库里没找到对应的节点。错误的节点编号请看日志。chatNode==null, nodeCode={}", nodeCode);
							sayText("节点编号可能填错了，数据库里没找到对应的节点。错误的节点编号请看日志。", true);
							return;
						}

						sayChatNode(chatNode);

						break;// 目前先选择直说第一条命中的建议
					}

				} else if ("Text".equals(type)) {

					String answerSource = message.getText().getAnswerSource();
					String content = message.getText().getContent();
					if ("ChitChat".equals(answerSource)) {
						// 没有命中知识库
						// 2.1)若没有精确命中知识库，则做情感判断，根据正面、负面、中性，来决定下一node
						logger.debug("没有命中知识库，也没有命中任何建议。机器人的闲聊回答是：[" + content + "]，这段回答不会被处理。");

						sentimentAnswer(text);

					} else if ("BotFramework".equals(answerSource)) {
						// 命中BotFramework意图
						nlpHit = NlpHit.BOT_FRAMEWORK;
						saveHumanChatNode(text, nlpHit);

						logger.info("系统说：命中了BotFramework中的意图。目前暂时没有使用botFramework。仅输出日志");
						logger.info("机器人说：" + content);
					} else {
						logger.warn("系统说：unknown answerSource: " + answerSource);
					}

				}

			} else {
				// 到这里表示机器人不知道该怎么回答，就根据情感回答吧
				logger.warn("系统说：message.size()==0， 表示机器人不知道该怎么回答。");
				sentimentAnswer(text);
			}
			// ===== NLP结束 =====
		} catch (Exception e) {
			this.sayText("看一下，抛异常拉", false);
			logger.error(e.getMessage(), e);
		}

	}

	@Override
	public void onHumanSilence(HumanSilenceEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {
			logger.info("系统说：检测到人类长时间静默。机器人主动说下一个主节点。");
			// 当客户静默时，机器人主动说下一个主节点
			RobotSession currentSession = getCurrentSession();
			ChatNodeEntity lastMainChatNodeEntity = currentSession.getLastMainChatNodeEntity();
			String nextMainNodeCode = lastMainChatNodeEntity.getNextMainNodeCode();
			ChatNodeEntity nextMainNode = sessionService.getChatNodeEntity(nextMainNodeCode);
			if (nextMainNode != null) {
				new Thread(() -> sayChatNode(nextMainNode)).start();
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onRobotSpeaking(RobotSpeakingEvent event) {
		// logger.debug(JSON.toJSONString(event));
		// 这个event太多，刷屏，不打日志了

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

			setLastSilenceTimestamp(0);

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onRobotSayNode(RobotSayNodeEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {
			logger.info("机器人说：{}, nodeId={}, nodeCode={}", event.getContentText(), event.getNodeId(),
					event.getNodeCode());

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onRobotSentenceComplete(RobotSentenceCompleteEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {
			setLastSilenceTimestamp(System.currentTimeMillis());
			// 机器人说完话
			logger.info("系统说：机器人说完了一句话，恢复ASR处理（说明：有可能之前机器人说的节点是不允许打断的，在说那个节点时会暂停ASR处理）");
			asrFlag = true;

			// 如果当前节点有后续action，则在说完话后立刻执行后续action
			ChatNodeEntity chatNodeEntity = getCurrentSession().getCurrentChatNodeEntity();
			String actionType = chatNodeEntity.getActionType();
			String actionValue = chatNodeEntity.getActionValue();
			logger.debug("actionType={}, actionValue={}", actionType, actionValue);
			if (StringUtils.isNotBlank(actionType)) {
				if (NodeType.CHAT_NODE.getType().equals(actionType)) {
					ChatNodeEntity node = sessionService.getChatNodeEntity(actionValue);// actionValue=nodeCode
					if (node == null) {
						sayText("节点为空，节点id为：" + actionValue, false);
					} else {
						sayChatNode(node);
					}
				} else if (NodeType.TRANSFER.getType().equals(actionType)) {
					// TODO vmw.redirect()

				} else if (NodeType.HANGUP.getType().equals(actionType)) {
					this.hangup();
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onSipRegistering(SipRegisteringEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onSipRegisterSuccessful(SipRegisterSuccessfulEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

			setRegisterSuccessful(true);

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onSipRegisterFailed(SipRegisterFailedEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

			setRegisterSuccessful(false);

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onSipRinging(SipRingingEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onSipIncomingCall(SipIncomingCallEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

			RobotSession robotSession = getNewSession();
			robotSession.setPhoneNumber(event.getPhoneNumber());
			robotSession.setCallId(event.getCallId());

			answer();
			startASR();

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {
			// 当有呼入通话时，自动接起
			// 如果需要模拟振铃后再接起，更贴近正常的通话体验，可以在这里sleep一下
			// Thread.sleep(2000);
			robotStartTalking();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

	}

	@Override
	public void onSipCalleePickup(SipCalleePickupEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

			setCalling(true);

			startASR();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

			robotStartTalking();

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onSipRemoteHangup(SipRemoteHangupEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

			hangup();

			stopASR();

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void onSipError(SipErrorEvent event) {
		logger.debug(JSON.toJSONString(event));

		// ====================
		// === 处理内部逻辑 ===
		// ====================
		try {

			RobotSdkServer.fireSdkEvent(JSON.toJSONString(event));

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		// ====================
		// === 处理业务逻辑 ===
		// ====================
		try {

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

}
